package com.box.app.arithmetic

private const val BACKQUOTES = "`([^`]{1,32767}?)`"

@Suppress("RegExpRedundantEscape")
private const val REGEX_BUILT_IN = """(\.|\*|\+|\-|\?|\||\[|\]|\(|\)|\{|\}|\\|\^|\$|\\b|\\w|\\s|\\d)"""
private val ESCAPE_PATTERN = Regex(REGEX_BUILT_IN)
private val NORMATIVE_PATTERN = Regex("($BACKQUOTES)|\\s{1,32767}")
private val TIGHTEN_UP_PATTERN = Regex("(,\\s)|($BACKQUOTES)|\\s{1,32767}")
private val BACKQUOTES_PATTERN = Regex(BACKQUOTES)
private val OPERATOR1 = join(mutableListOf(*Operator1.values()))
private val OPERATOR2 = join(mutableListOf(*Operator2.values()))
private val LEFT = Curve.left.escapeRegex()
private val RIGHT = Curve.right.escapeRegex()
private val VARIABLE = "(?:($BACKQUOTES)|(((?!(?:$LEFT|$RIGHT|$OPERATOR1|$OPERATOR2|`)).){1,32767}))"

internal expect object ParserFactory {
    @Throws(ArithmeticError::class)
    fun create(expression: CharSequence, callables: List<Callable>): Parser
}

class Parser internal constructor(expression: CharSequence, private val callables: List<Callable>) {
    private val expression = expression.normative()
    private val factorPattern: Regex
        get() {
            val subexpression = recursive(parseCurves(this.expression) - 1)
            val symbol = join(this.callables.names())
            return Regex(
                listOf(
                    "($LEFT(?<expression>($subexpression)*?)$RIGHT)",
                    "(?<symbol>(?:(?:$symbol)|`(?:$symbol)`)(?=$LEFT($subexpression)*?$RIGHT))",
                    "(?<variable>$VARIABLE)",
                    "(?<!(?:$VARIABLE|$RIGHT))(?<operator1>(?:$OPERATOR1))(?=(?:$LEFT|$VARIABLE|$OPERATOR1))",
                    "(?<operator2>(?:$OPERATOR2))",
                    "(?<curve>(?:$LEFT|$RIGHT))",
                ).joinToString("|")
            )
        }

    @Throws(ArithmeticError::class)
    fun parseExpression(expression: CharSequence = this.expression.tightenUp()): Expression {
        val matches = factorPattern.findAll(expression).toMutableList()
        val factors = mutableListOf<Factor>()
        val length = expression.length
        var index = 0
        while (index < length) {
            val next = matches.firstOrNull()?.range?.first ?: length
            index = if (index >= next) {
                val result = matches.removeFirst()
                factors.addAll(parseFactors(result))
                result.range.last + 1
            } else {
                val end = this.expression.indexOf(expression.substring(index, next))
                throw ArithmeticError.character(this.expression, end, next + end - index)
            }
        }
        return Expression(factors)
    }

    private fun mathOf(symbol: String) = callables.maybeOf(symbol.backquotes()) ?: Math.of(symbol)

    @Throws(ArithmeticError::class)
    private fun parseFactors(result: MatchResult): List<Factor> {
        val factors = mutableListOf<Factor>()
        val group = result.groups as? MatchNamedGroupCollection
        val expression = group?.get("expression")?.value
        val symbol = group?.get("symbol")?.value
        val variable = group?.get("variable")?.value
        val operator1 = group?.get("operator1")?.value
        val operator2 = group?.get("operator2")?.value
        val curve = group?.get("curve")?.value
        if (expression != null) {
            factors.addAll(Curve.wrap(Variables.of(expression, this)))
        }
        if (symbol != null) {
            factors.add(mathOf(symbol))
        }
        if (variable != null) {
            factors.add(variablesOf(variable))
        }
        if (operator1 != null) {
            factors.add(Operator1.of(operator1))
        }
        if (operator2 != null) {
            factors.add(Operator2.of(operator2))
        }
        if (curve != null) {
            factors.add(Curve.of(curve))
        }
        return factors
    }

    private fun recursive(maxDepth: Int): String {
        if (maxDepth <= 0) {
            return "."
        }
        val center = "(((?!(?:$LEFT|$RIGHT)).){1,32767})"
        val initial = "$LEFT.*?$center*?$RIGHT"
        val leading = ".*?(?:$center?|"
        val trailing = ")*?"

        val buffer = StringBuilder(initial)
        var depth = 0
        fun recursion() {
            buffer.insert(0, "$LEFT$leading")
            buffer.append("$trailing$RIGHT")
            depth++
            if (depth < maxDepth) {
                recursion()
            }
        }
        try {
            recursion()
        } catch (ignored: Throwable) {
        }
        return "$leading$buffer$trailing"
    }

    companion object {
        @Throws(ArithmeticError::class)
        fun parse(expression: CharSequence, vararg callables: Callable): Expression {
            return parse(expression, listOf(*callables))
        }

        @Throws(ArithmeticError::class)
        fun parse(expression: CharSequence, callables: List<Callable>): Expression {
            return ParserFactory.create(expression, callables).parseExpression()
        }
    }
}

private fun join(values: List<Any>) = values.sortedByDescending { "$it".length }.joinToString("|") { it.escapeRegex() }

private fun Any.escapeRegex() = toString().replace(ESCAPE_PATTERN, "\\\\$1")

private fun CharSequence.normative(): String {
    return NORMATIVE_PATTERN.replace(this) {
        return@replace it.groups[1]?.value ?: " "
    }
}

private fun CharSequence.tightenUp(): String {
    return TIGHTEN_UP_PATTERN.replace(this) {
        val value1 = it.groups[1]?.value
        val value2 = it.groups[2]?.value
        return@replace value1 ?: value2 ?: ""
    }
}

private fun CharSequence.backquotes(): String {
    return BACKQUOTES_PATTERN.replace(this) {
        return@replace it.groups[1]?.value ?: it.value
    }
}

private fun variablesOf(variable: String) = Variable1.of(variable.backquotes())

@Throws(ArithmeticError::class)
private fun parseCurves(expression: String): Int {
    var maxDepth = 0
    var depth = 0
    val indexes = mutableListOf<Int>()
    val length = expression.length
    for (index in 0 until length) {
        val c = expression[index]
        if (Curve.left.toString() == c.toString()) {
            depth++
            indexes.add(index)
            if (depth > maxDepth) {
                maxDepth = depth
            }
        } else if (Curve.right.toString() == c.toString()) {
            depth--
            if (depth < 0) {
                indexes.add(index)
            } else {
                indexes.removeLastOrNull()
            }
        }
    }
    return if (depth == 0) {
        maxDepth
    } else {
        throw ArithmeticError.curve(depth, expression, indexes.first(), indexes.last() + 1)
    }
}